package com.tanhua.sso.service;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.tanhua.common.mapper.AnalysisByDayMapper;
import com.tanhua.common.mapper.DayMapper;
import com.tanhua.common.mapper.RetentionldMapper;
import com.tanhua.common.mapper.UserMapper;
import com.tanhua.common.pojo.AnalysisByDay;
import com.tanhua.common.pojo.Day;
import com.tanhua.common.pojo.Retentionld;
import com.tanhua.common.pojo.User;
import com.tanhua.dubbo.server.api.HuanXinApi;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Reference(version = "1.0.0")
    private HuanXinApi huanXinApi;

    @Autowired
    private AnalysisByDayMapper analysisByDayMapper;

    @Autowired
    private RetentionldMapper retentionldMapper;

    @Autowired
    private DayMapper dayMapper;


    /**
     * 用户登录
     *
     * @param phone 手机号
     * @param code  验证码
     * @return
     */
    public String login(String phone, String code) {
        String redisKey = "CHECK_CODE_" + phone;
        boolean isNew = false;

        //校验验证码
        String redisData = this.redisTemplate.opsForValue().get(redisKey);
        if (!StringUtils.equals(code, redisData)) {
            return null; //验证码错误
        }

        //验证码在校验完成后，需要废弃
        this.redisTemplate.delete(redisKey);

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("mobile", phone);

        User user = this.userMapper.selectOne(queryWrapper);

        if (null == user) {
            //需要注册该用户
            user = new User();
            user.setMobile(phone);
            user.setPassword(DigestUtils.md5Hex("123456"));

            //注册新用户
            this.userMapper.insert(user);
            isNew = true;

            //将该用户信息注册到环信平台
            Boolean register = this.huanXinApi.register(user.getId());
            if (!register) {
                log.error("注册到环信平台失败！ " + user.getId());
            }
        }

        //生成token
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("id", user.getId());

        // 生成token
        String token = Jwts.builder()
                .setClaims(claims) //payload，存放数据的位置，不能放置敏感数据，如：密码等
                .signWith(SignatureAlgorithm.HS256, secret) //设置加密方法和加密盐
                .setExpiration(new DateTime().plusHours(12).toDate()) //设置过期时间，12小时后过期
                .compact();

        /**
         * 刘波9/14
         */
        //查询并修改登录次数
        LambdaQueryWrapper<AnalysisByDay> lambdaQueryWrapper = new LambdaQueryWrapper();
        String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        lambdaQueryWrapper.eq(AnalysisByDay::getRecordDate, today);
        AnalysisByDay analysisByDay = this.analysisByDayMapper.selectOne(lambdaQueryWrapper);

        LambdaUpdateWrapper<AnalysisByDay> lambdaUpdateWrapper = new LambdaUpdateWrapper();
        lambdaUpdateWrapper.eq(AnalysisByDay::getRecordDate,today);
        AnalysisByDay analysisByDay1 = new AnalysisByDay();
        analysisByDay1.setNumLogin(analysisByDay.getNumLogin()+1);
        this.analysisByDayMapper.update(analysisByDay1,lambdaUpdateWrapper);

        //活跃人数
        String format1 = new SimpleDateFormat("yyyy-MM-dd").format(user.getUpdated());
        if(!format1.equals(analysisByDay.getRecordDate())){
            LambdaUpdateWrapper<AnalysisByDay> lambdaUpdateWrapper1 = new LambdaUpdateWrapper();
            lambdaUpdateWrapper1.eq(AnalysisByDay::getRecordDate,today);
            AnalysisByDay analysisByDay2 = new AnalysisByDay();
            analysisByDay2.setNumActive(analysisByDay.getNumActive()+1);
            this.analysisByDayMapper.update(analysisByDay2,lambdaUpdateWrapper1);
        }
        LambdaUpdateWrapper<User> userWrapper = new LambdaUpdateWrapper();
        userWrapper.eq(User::getMobile,phone);
        User user1 = new User();
        user1.setUpdated(new Date());
        this.userMapper.update(user1,userWrapper);


        long daytime = 24*60*60*1000;
        //次日留存数
        long creatTime  = user.getCreated().getTime();
        long updateTime = user.getUpdated().getTime();
        long chajuTime = updateTime-creatTime;
        if(chajuTime > daytime && chajuTime < (daytime*(2))){
            LambdaUpdateWrapper<AnalysisByDay> retention1dWrapper = new LambdaUpdateWrapper();
            retention1dWrapper.eq(AnalysisByDay::getRecordDate,today);
            AnalysisByDay analysisByDayRetentionld = new AnalysisByDay();
            analysisByDayRetentionld.setNumRetention1d(analysisByDay.getNumRetention1d()+1);
            this.analysisByDayMapper.update(analysisByDayRetentionld,retention1dWrapper);
        }


        //用户注册时间和day表日期对应  creatTime
        String creatFormat = new SimpleDateFormat("yyyy-MM-dd").format(creatTime);
        //将数据存入day表
        switch ((int)(chajuTime/daytime)){
            case 1 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setOne(day1.getOne()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterOne(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }
            case 2 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setTwo(day1.getTwo()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterTwo(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }case 3 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setThree(day1.getThree()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterThree(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }case 4 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setFoul(day1.getFoul()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterFoul(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }case 5 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setFive(day1.getFive()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterFive(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }case 6 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setSix(day1.getSix()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterSix(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);

                break;
            }case 7 :{
                LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
                dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
                Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

                LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
                dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
                Day day2 = new Day();
                day2.setSeven(day1.getSeven()+1);
                this.dayMapper.update(day2,dayUpdateWrapper);

                LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
                rlqw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

                LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
                rluw.eq(Retentionld::getDate,creatFormat);
                Retentionld retentionld1 = new Retentionld();
                float v = (float)day1.getOne()/retentionld.getRegistered();
                int num = (int)(v*100);
                retentionld1.setAfterSeven(num+"%");
                this.retentionldMapper.update(retentionld1,rluw);
                break;
            }
            default:
                break;
        }
        if((int)(chajuTime/daytime) > 7){
            LambdaQueryWrapper<Day> dayQueryWrappper = new LambdaQueryWrapper<>();
            dayQueryWrappper.eq(Day::getRecordDate,creatFormat);
            Day day1 = this.dayMapper.selectOne(dayQueryWrappper);

            LambdaUpdateWrapper<Day> dayUpdateWrapper = new LambdaUpdateWrapper<>();
            dayUpdateWrapper.eq(Day::getRecordDate,creatFormat);
            Day day2 = new Day();
            day2.setTheirth(day1.getTheirth()+1);
            this.dayMapper.update(day2,dayUpdateWrapper);

            LambdaQueryWrapper<Retentionld> rlqw= new LambdaQueryWrapper<>();
            rlqw.eq(Retentionld::getDate,creatFormat);
            Retentionld retentionld = this.retentionldMapper.selectOne(rlqw);

            LambdaUpdateWrapper<Retentionld> rluw = new LambdaUpdateWrapper<>();
            rluw.eq(Retentionld::getDate,creatFormat);
            Retentionld retentionld1 = new Retentionld();
            float v = (float)day1.getOne()/retentionld.getRegistered();
            int num = (int)(v*100);
            retentionld1.setAfterMonth(num+"%");
            this.retentionldMapper.update(retentionld1,rluw);
        }

        /**
         * 刘波9/14
         */
        try {
            //发送用户登录成功的消息
            Map<String, Object> msg = new HashMap<>();
            msg.put("id", user.getId());
            msg.put("date", System.currentTimeMillis());

            this.rocketMQTemplate.convertAndSend("tanhua-sso-login", msg);
        } catch (MessagingException e) {
            log.error("发送消息失败！", e);
        }

        return token + "|" + isNew;
    }

    public User queryUserByToken(String token) {
        try {
            // 通过token解析数据
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

            User user = new User();
            user.setId(Long.valueOf(body.get("id").toString()));

            //需要返回user对象中的mobile，需要查询数据库获取到mobile数据
            //如果每次都查询数据库，必然会导致性能问题，需要对用户的手机号进行缓存操作
            //数据缓存时，需要设置过期时间，过期时间要与token的时间一致
            //如果用户修改了手机号，需要同步修改redis中的数据

            String redisKey = "TANHUA_USER_MOBILE_" + user.getId();
            if (this.redisTemplate.hasKey(redisKey)) {
                String mobile = this.redisTemplate.opsForValue().get(redisKey);
                user.setMobile(mobile);
            } else {
                //查询数据库
                User u = this.userMapper.selectById(user.getId());
                user.setMobile(u.getMobile());

                //将手机号写入到redis中
                //在jwt中的过期时间的单位为：秒
                long timeout = Long.valueOf(body.get("exp").toString()) * 1000 - System.currentTimeMillis();
                this.redisTemplate.opsForValue().set(redisKey, u.getMobile(), timeout, TimeUnit.MILLISECONDS);
            }

            return user;
        } catch (ExpiredJwtException e) {
            log.info("token已经过期！ token = " + token);
        } catch (Exception e) {
            log.error("token不合法！ token = " + token, e);
        }
        return null;
    }

    public Boolean updatePhone(Long userId, String newPhone) {
        //先查询新手机号是否已经注册，如果已经注册，就不能修改
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("mobile", newPhone);
        User user = this.userMapper.selectOne(queryWrapper);
        if(ObjectUtil.isNotEmpty(user)){
            //新手机号已经被注册
            return false;
        }

        user = new User();
        user.setId(userId);
        user.setMobile(newPhone);

        return this.userMapper.updateById(user) > 0;
    }
}
